package com.huli.utils.cos;

import com.huli.constant.Constant;
import com.qcloud.cos.COSClient;
import com.qcloud.cos.ClientConfig;
import com.qcloud.cos.auth.BasicCOSCredentials;
import com.qcloud.cos.auth.COSCredentials;
import com.qcloud.cos.exception.CosClientException;
import com.qcloud.cos.exception.CosServiceException;
import com.qcloud.cos.exception.MultiObjectDeleteException;
import com.qcloud.cos.model.*;
import com.qcloud.cos.region.Region;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * @author Squbi
 * @date 2020/8/24 23:23
 * @Description 该套API考虑到权限问题 所以不提供桶级操作 如有需求请自行查询官方API文档
 * 桶级操作:https://cloud.tencent.com/document/product/436/35214
 * 更多操作:https://cloud.tencent.com/document/product/436/35215
 */
@Component
public class COSAPI {

    //COS的secretID
    @Value(Constant.COS_SECRET_ID)
    private String secretId;
    //COS的secretKEY
    @Value(Constant.COS_SECRET_KEY)
    private String secretKey;
    //存储所在地域region
    @Value(Constant.COS_REGION)
    private String region;
    //bucket的命名规则为{name}-{appId} ，此处填写的存储桶名称必须为此格式
    @Value(Constant.COS_BUCKET_NAME)
    private String bucketName;

    public COSClient getCosClient() {
        return cosClient;
    }

    public void setCosClient(COSClient cosClient) {
        this.cosClient = cosClient;
    }

    public String getSecretId() {
        return secretId;
    }

    public void setSecretId(String secretId) {
        this.secretId = secretId;
    }

    public String getSecretKey() {
        return secretKey;
    }

    public void setSecretKey(String secretKey) {
        this.secretKey = secretKey;
    }

    public String getRegion() {
        return region;
    }

    public void setRegion(String region) {
        this.region = region;
    }

    //设置一个空的COSClient getCOSClient方法中判断该是否为空 如果空则创建 非空则使用
    private COSClient cosClient = null;

    public COSAPI(String secretId, String secretKey, String bucketName) {
        this.secretId = secretId;
        this.secretKey = secretKey;
        this.bucketName = bucketName;
    }

    public COSAPI() {

    }

    /**
     * @author Squbi
     * @date 2020/8/24 23:14
     * @Description 重新设置BUCKET_NAME 也就是切换桶目标
     */
    public COSAPI setBucketName(String bucketName) {
        this.bucketName = bucketName;
        return this;
    }

    public String getBucketName() {
        return this.bucketName;
    }

    /**
     * @author Squbi
     * @date 2020/8/21 18:40
     * @Description 获取COSClient
     */
    public COSClient getCOSClient() {
        if (cosClient == null) {
            // 1 初始化用户身份信息(secretId, secretKey)
            COSCredentials cred = new BasicCOSCredentials(secretId, secretKey);
            // 2 设置bucket的地域简称(参照下图)
            ClientConfig clientConfig = new ClientConfig(new Region(region));
            // 3 生成cos客户端
            return new COSClient(cred, clientConfig);
        } else {
            return this.cosClient;
        }
    }

    /**
     * @author Squbi
     * @date 2020/8/21 18:40
     * @Description 设置ObjectMetadata并返回ObjectMetadata
     */
    private ObjectMetadata getObjectMetadata(String contentType, long contentLength) {
        ObjectMetadata objectMetadata = new ObjectMetadata();
        objectMetadata.setContentType(contentType);
        objectMetadata.setContentLength(contentLength);
        return objectMetadata;
    }

    private ObjectMetadata getObjectMetadata(long contentLength) {
        ObjectMetadata objectMetadata = new ObjectMetadata();
        objectMetadata.setContentLength(contentLength);
        return objectMetadata;
    }

    /**
     * @author Squbi
     * @date 2020/8/24 22:30
     * @Description 通过stream上传文件 contentType默认为 application/octet-stream 如果不想设置 使用默认值 传入null即可
     */
    public PutObjectResult uploadByStream(String filename, InputStream inputStream, String contentType, long contentLength) {
        //获取COSClient
        COSClient cosClient = getCOSClient();
        //设置格式
        ObjectMetadata objectMetadata;
        if (contentType != null) {
            objectMetadata = this.getObjectMetadata(contentType, contentLength);
        } else {
            objectMetadata = this.getObjectMetadata(contentLength);
        }
        // putObject 设置 1 bucket->存储桶名字 2 key->存储键 也就是文件名 不可重复
        // 3 inputStream已通过传入对象获取 4 objectMetadata 传输对象属性
        PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, filename, inputStream, objectMetadata);

        return cosClient.putObject(putObjectRequest);
    }

    /**
     * @author Squbi
     * @date 2020/10/12 15:41
     * @Description 对MultipartFile进行再封装
     */
    public PutObjectResult uploadByMultipartFile(MultipartFile multipartFile, String filename) throws IOException {
        return uploadByStream(filename,multipartFile.getInputStream(),null,multipartFile.getSize());
    }

    /**
     * @author Squbi
     * @date 2020/8/24 22:34
     * @Description 通过本地文件路径上传文档
     */
    public PutObjectResult uploadByFile(String filename, String localFilePath) {
        //获取到cosClient
        COSClient cosClient = this.getCOSClient();
        // putObject 设置 1 bucket->存储桶名字 2 key->存储键 也就是文件名 不可重复 3 tempFile 本地文件对象
        PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, filename, new File(localFilePath));
        //执行
        return cosClient.putObject(putObjectRequest);
    }

    /**
     * @author Squbi
     * @date 2020/8/24 22:37
     * @Description prefix为查询前缀 maxKeys为最大遍历对象数量(不可超过1000)
     * 文件的路径 cosObjectSummary.getKey();
     * 文件的etag cosObjectSummary.getETag();
     * 文件的长度 cosObjectSummary.getSize();
     * 文件的存储类型 cosObjectSummary.getStorageClass();
     */
    public List<COSObjectSummary> selectObject(String prefix, int maxKeys) {
        //设置请求
        ListObjectsRequest listObjectsRequest = new ListObjectsRequest();
        // 设置bucket名称 前缀 最大查询数
        listObjectsRequest.setBucketName(bucketName);
        listObjectsRequest.setPrefix(prefix);
        listObjectsRequest.setMaxKeys(maxKeys);
        // deliter表示分隔符, 设置为/表示列出当前目录下的object, 设置为空表示列出所有的object
        listObjectsRequest.setDelimiter("/");
        //do while循环 以保证查询结果完整返回
        ObjectListing objectListing = null;
        //定义总对象集合用于存储数据
        List<COSObjectSummary> cosObject = new ArrayList<>();
        do {
            try {
                //执行请求
                objectListing = this.getCOSClient().listObjects(listObjectsRequest);
            } catch (CosServiceException e) {
                e.printStackTrace();
                return null;
            } catch (CosClientException e) {
                e.printStackTrace();
                return null;
            }
            // object summary表示所有列出的object列表
            List<COSObjectSummary> cosObjectSummaries = objectListing.getObjectSummaries();
            cosObject.addAll(cosObjectSummaries);
            //在通过得到当前的maker继续下一次查询
            String nextMarker = objectListing.getNextMarker();
            listObjectsRequest.setMarker(nextMarker);
        } while (objectListing.isTruncated());
        //最后返回总对象集合
        return cosObject;
    }

    /**
     * @author Squbi
     * @date 2020/8/24 23:05
     * @Description 通过ObjectKey 也就是桶中的名字得到该对象的输入流 记得关流
     */
    public COSObjectInputStream getCOSObjectInputStream(String key) {
        //创建请求对象
        GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, key);
        //得到请求的COSObject
        COSObject cosObject = this.getCOSClient().getObject(getObjectRequest);
        //通过该对象得到Stream并返回
        return cosObject.getObjectContent();
    }

    /**
     * @author Squbi
     * @date 2020/8/24 23:11
     * @Description 通过目标对象名下载到本地路径
     */
    public ObjectMetadata download(String key, String localPath) {
        File downFile = new File(localPath);
        GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, key);
        return this.getCOSClient().getObject(getObjectRequest, downFile);
    }

    /**
     * @author Squbi
     * @date 2020/8/24 23:17
     * @Description 通过key删除对象
     */
    public void deleteObject(String key) {
        this.getCOSClient().deleteObject(bucketName, key);
    }

    /**
     * @author Squbi
     * @date 2020/8/24 23:39
     * @Description 批量删除 需要传入一个keys的集合 也就是文件名集合
     */
    public List<DeleteObjectsResult.DeletedObject> deleteObjects(ArrayList<String> keys) {
        //设置删除请求
        DeleteObjectsRequest deleteObjectsRequest = new DeleteObjectsRequest(bucketName);
        // 设置要删除的key列表, 最多一次删除1000个
        ArrayList<DeleteObjectsRequest.KeyVersion> keyList = new ArrayList<DeleteObjectsRequest.KeyVersion>();
        // 传入要删除的文件名
        for (String key : keys) {
            keyList.add(new DeleteObjectsRequest.KeyVersion(key));
        }
        //将该集合设置入Request中
        deleteObjectsRequest.setKeys(keyList);
        // 批量删除文件
        List<DeleteObjectsResult.DeletedObject> deleteObjectResultArray = null;
        try {
            //执行删除 得到返回的结果集
            DeleteObjectsResult deleteObjectsResult = this.getCOSClient().deleteObjects(deleteObjectsRequest);
            //通过该结果集得到删除的结果
            deleteObjectResultArray = deleteObjectsResult.getDeletedObjects();
        } catch (MultiObjectDeleteException mde) {
            // 如果部分删除成功部分失败, 返回MultiObjectDeleteException
            List<DeleteObjectsResult.DeletedObject> deleteObjects = mde.getDeletedObjects();
            List<MultiObjectDeleteException.DeleteError> deleteErrors = mde.getErrors();
        } catch (CosServiceException e) {
            // 如果是其他错误，例如参数错误， 身份验证不过等会抛出 CosServiceException
            e.printStackTrace();
            throw e;
        } catch (CosClientException e) {
            // 如果是客户端错误，例如连接不上COS
            e.printStackTrace();
            throw e;
        }
        //返回删除的结果
        return deleteObjectResultArray;
    }

    /**
     * @author Squbi
     * @date 2020/8/24 23:20
     * @Description 关闭当前的Client
     */
    public void shutdown() {
        this.getCOSClient().shutdown();
    }
}
